package org.windowkit.toolkit.widget.treetable;

import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.print.PrinterException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JInternalFrame;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JRootPane;
import javax.swing.JScrollPane;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TreeSelectionListener;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableColumnModel;

import org.ddevil.data.BasicData;
import org.ddevil.data.BasicDisplayDescriptor;
import org.ddevil.data.Data;
import org.ddevil.data.DisplayDescriptor;
import org.ddevil.data.KeyedChangeTracker.KeyedChange;
import org.ddevil.data.set.BasicDataSet;
import org.ddevil.data.set.BasicSortableDataSet;
import org.ddevil.data.set.DataSet;
import org.ddevil.data.util.DataUtils;
import org.windowkit.toolkit.event.EventDispatcher;
import org.windowkit.toolkit.print.PrintUtility;
import org.windowkit.toolkit.print.PrintableComponent;
import org.windowkit.toolkit.util.LayoutUtils;
import org.windowkit.toolkit.widget.table.RecordTablePane;


/**
 * This component displays a tree only each node is broken into columns to allow
 * the user to easily edit single values on a node instead of the old way of
 * displaying nodes as |-"data1/data2/data3" and giving text boxes to edit with.
 *
 * @author Eric Lundin
 */
public class TreeTablePane extends JComponent implements PrintableComponent, ComponentListener {

	/**
	 *
	 */
	private static final long serialVersionUID = 7823874890018539683L;

	/** The text for expanding the entire tree from a popup menu. */
	private static final String EXPAND_ENTIRE_TREE = "Expand Tree";

	/** The text for collapsing the entire tree from a popup menu. */
	private static final String COLLAPSE_ENTIRE_TREE = "Collapse Tree";

	/**
	 *
	 */
	private static final String TABLE_HELP_COMMAND = "Table Help";

	/** The table which will display the tree which will display the data */
	TreeTable table;

	/** The scroll pane so we can see all of this mess. */
	JScrollPane scrollPane;

	EventDispatcher eventDispatcher;

	/** The unique component id for this instance. */
	private String componentId;

	PopupMenuListener popupListener;

	protected JPopupMenu popupMenu;

	protected JPopupMenu headerMenu;

	private int[] preprintSelectionRows;

	private String printTitle;

	boolean debug = false;

	private List<String> debugTableIds;

	public TreeTablePane() {
		init();
		layoutComponents();
		setPopupMenu(null);
		addComponentListener(this);
		applyHeaderPopupMenu();
	}

	/**
	 * Instantiate/initialize the various objects needed to make this component
	 * work.
	 */
	private void init() {
		table = new TreeTable();
		scrollPane = new JScrollPane(table);
		eventDispatcher = new EventDispatcher(this);
		table.getSelectionModel().addListSelectionListener(eventDispatcher);
		table.addTreeSelectionListener(eventDispatcher);
	}

	public void setData(final BasicSortableDataSet ds) {
		table.setData(ds);
		if (debug) {
			setDebugIds(ds.getIdentifiers());
		}
	}

	private void setDebugIds(final List<String> ids) {
		debugTableIds = ids;
	}

	/**
	 * Layout the components.
	 */
	private void layoutComponents() {
		setLayout(new GridBagLayout());
		this.add(scrollPane, LayoutUtils.createConstraintsFillBoth());
		scrollPane.getViewport().setBackground(table.getBackground());
		scrollPane
				.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
		scrollPane
				.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
	}

	/**
	 * Sets the labels which will determine which columns are displayed.
	 *
	 * @param labels
	 *            The display descriptor which describes which columns will be
	 *            shown.
	 * @param identifier
	 *            THe identifier for this Tree Table.
	 */
	public void setLabelsAndKeys(final DisplayDescriptor labels,
			final ArrayList<ArrayList<String>> keys, final String identifier) {
		((TreeTableModelAdapter) table.getModel()).setLabelsAndKeys(labels,
				keys);
		setIdentifier(identifier);
	}

	/**
	 * Set which attributes to export per level. This is where to tell the tree
	 * to export non key data such as time keys.
	 *
	 * @param exportAttribs
	 */
	public void setChangeExportKeys(final ArrayList<ArrayList<String>> exportAttribs) {
		table.setChangeExportKeys(exportAttribs);
	}

	public void setEditableColumns(final ArrayList<String> editableColumns) {
		table.setEditableColumns(editableColumns);
	}

	public void setColumnEditor(final String dbId, final TableCellEditor editor) {
		table.setColumnEditor(dbId, editor);
	}

	/**
	 * Set the popup menu to the one given. Additionally items for printing and
	 * expanding the entire tree will be added.
	 *
	 * @param menu
	 */
	public void setPopupMenu(final JPopupMenu menu) {
		if (popupListener == null) {
			popupListener = new PopupMenuListener();
			scrollPane.getViewport().addMouseListener(popupListener);
			scrollPane.addMouseListener(popupListener);
			table.addMouseListener(popupListener);
		}
		if (menu == null) {
			popupMenu = new JPopupMenu();
		} else {
			popupMenu = menu;
			popupMenu.addSeparator();
		}
		JMenuItem currentItem;

		currentItem = new JMenuItem(EXPAND_ENTIRE_TREE);
		currentItem.setActionCommand(EXPAND_ENTIRE_TREE);
		currentItem.addActionListener(popupListener);
		popupMenu.add(currentItem);

		currentItem = new JMenuItem(COLLAPSE_ENTIRE_TREE);
		currentItem.setActionCommand(COLLAPSE_ENTIRE_TREE);
		currentItem.addActionListener(popupListener);
		popupMenu.add(currentItem);

		popupMenu.addSeparator();

		currentItem = new JMenuItem(PrintableComponent.PRINT_COMMAND);
		currentItem.setActionCommand(PrintableComponent.PRINT_COMMAND);
		currentItem.addActionListener(popupListener);
		popupMenu.add(currentItem);

		currentItem = new JMenuItem(PrintableComponent.PRINT_PREVIEW_COMMAND);
		currentItem.setActionCommand(PrintableComponent.PRINT_PREVIEW_COMMAND);
		currentItem.addActionListener(popupListener);
		popupMenu.add(currentItem);

		popupMenu.addSeparator();

		currentItem = new JMenuItem(TABLE_HELP_COMMAND);
		currentItem.setActionCommand(TABLE_HELP_COMMAND);
		currentItem.addActionListener(popupListener);
		popupMenu.add(currentItem);

		if (debug) {
			currentItem = new JMenuItem("View Changes");
			currentItem.setActionCommand("View Changes");
			currentItem.addActionListener(popupListener);
			popupMenu.add(currentItem);
		}
	}

	private void applyHeaderPopupMenu() {
		JMenuItem currentItem;
		headerMenu = new JPopupMenu();
//		currentItem = new JMenuItem(PreferenceUtils.SAVE_PREFERENCES);
//		currentItem.addActionListener(popupListener);
//		headerMenu.add(currentItem);

//		currentItem = new JMenuItem(PreferenceUtils.LOAD_DEFAULT);
//		currentItem.addActionListener(popupListener);
//		headerMenu.add(currentItem);
//		table.getTableHeader().addMouseListener(popupListener);
	}

	public List<KeyedChange> getChanges() {
		return table.getChanges();
	}

	public boolean hasChanges() {
		return table.getChanges().size() > 0;
	}

	/*
	 * (non-Javadoc)
	 */
	public String getIdentifier() {
		return componentId;
	}

	/*
	 */
	public void setIdentifier(final String s) {
		boolean idChanged = !DataUtils.safeEquals(componentId, s);
		componentId = s;
//		if (idChanged) {
//			defaultPreference = createTablePreference();
//			getTablePreference();
//		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.windowkit.toolkit.print.PrintableComponent#getPrintComponent()
	 */
	public JComponent getPrintComponent() {
		return table;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.windowkit.toolkit.print.PrintableComponent#getPrintHeader()
	 */
	public JComponent getPrintHeader() {
		return table.getTableHeader();
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.windowkit.toolkit.print.PrintableComponent#prepareForPrint()
	 */
	public void prepareForPrint() {
		preprintSelectionRows = table.getSelectedRows();
		table.clearSelection();
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.windowkit.toolkit.print.PrintableComponent#restorePrePrintState()
	 */
	public void restorePrePrintState() {
		table.clearSelection();
		for (int preprintSelectionRow : preprintSelectionRows) {
			table.getSelectionModel().addSelectionInterval(
					preprintSelectionRow, preprintSelectionRow);
		}
	}

	public ArrayList<String> getSelectedRowConnectIds() {
		return table.getSelectedRowConnectIds();
	}

	public ArrayList<Integer> getSelectedLevels() {
		return table.getSelectedLevels();
	}

	public DataSet getSelectedRowData() {
		return table.getSelectedRowData();
	}

	public boolean isDataSelected() {
		return table.getSelectedRowCount() > 0;
	}

	public int getSelectedRowCount() {
		return table.getSelectedRowCount();
	}

//	private void getTablePreference() {
//		PreferenceManager manager = PreferenceManager
//				.getPreferenceManager(new DefaultModel(ServerReference
//						.getInstance()));
//		// We love Object[]
//		Object obj[] = manager.getPreference(getIdentifier());
//		if (obj != null) {
//			applyTablePreference((TablePreference) obj[0]);
//		}
//	}
//
//	private void applyTablePreference(TablePreference pref) {
//		TableColumnModel tcm = table.getColumnModel();
//		TableColumn tableColumn;
//		while (tcm.getColumnCount() > 0) {
//			tcm.removeColumn(tcm.getColumn(0));
//		}
//		ArrayList<String> ids = table.getDisplayDescriptor().getAllIds();
//		LinkedList<ColumnPreference> colPrefs = pref
//				.getTablePreferenceColumns();
//		for (int i = 0; i < colPrefs.size(); i++) {
//			tableColumn = new TableColumn(i, colPrefs.get(i).getColWidth());
//			tableColumn.setHeaderValue(table.getDisplayDescriptor().getLabel(
//					ids.get(i)));
//			tcm.addColumn(tableColumn);
//		}
//		table.setColumnModel(tcm);
//		resizeColumns();
//	}
//
//	protected void saveTablePreference() {
//		if (PreferenceUtils.showConfirmDialog(this)) {
//			TablePreference newTablePreference = createTablePreference();
//			if (newTablePreference.equals(defaultPreference)) {
//				PreferenceUtils.showSameAsDefaulDialog(this);
//				return;
//			} else if (newTablePreference.equals(userPreference)) {
//				PreferenceUtils.showNoChangeDialog(this);
//			}
//			PreferenceManager.cachePreference(getIdentifier(),
//					newTablePreference);
//			PreferenceUtils.showPrefSavedDialog(this);
//		}
//	}
//
//	protected void loadDefaultPreference() {
//		applyTablePreference(defaultPreference);
//	}
//
//	protected TablePreference createTablePreference() {
//		TablePreference pref = new TablePreference(getIdentifier());
//		ArrayList<String> ids = table.getDisplayDescriptor().getAllIds();
//		TableColumnModel tcm = table.getColumnModel();
//		for (int i = 0; i < ids.size(); i++) {
//			pref.addTablePreferenceColumn(ids.get(i), tcm.getColumn(i)
//					.getWidth());
//		}
//		return pref;
//	}

	/**
	 * Remove the row with the specified attributes from the table.
	 *
	 * @see STreeTable#deleteRow(DataSet)
	 */
	public boolean deleteRow(final Data atts, final int level) {
		return table.deleteRow(atts, level);
	}

	public void deleteRows(final DataSet ds, final int level) {
		table.deleteRows(ds, level);
	}

	public boolean modifyRecord(final Data oldValue, final String dbId,
			final Object value, final int level) {
		return table.modifyRecord(oldValue, dbId, value, level);
	}

	/**
	 * Remove the rows with the sepcified attributes from the table.
	 *
	 * @see STreeTable#deleteRow(Data)
	 */
	public void clearData() {
		table.clearData();
	}

	/**
	 * Resize the table
	 */
	protected void resize() {
		JRootPane rootPane = SwingUtilities.getRootPane(this);
		resizeTable();
		resizeColumns();
		if (rootPane != null) {
			rootPane.repaint();
		}
	}

	/**
	 * Resize the columns of the table.
	 */
	protected void resizeColumns() {
		TableColumnModel tcm = table.getColumnModel();
		if (tcm.getColumnCount() == 0) {
			return;
		}
		double viewWidth;
		double scaleFactor;
		int totalColumnWidth = 0;
		int newWidth;
		int totalNewWidth = 0;
		viewWidth = scrollPane.getViewport().getWidth();
		totalColumnWidth = tcm.getTotalColumnWidth();
		if (totalColumnWidth < viewWidth) {
			scaleFactor = viewWidth / totalColumnWidth;
			for (int i = 0; i < tcm.getColumnCount(); i++) {
				newWidth = (int) (/* userChosenColSizes[activeColumns[i]] */tcm
						.getColumn(i).getPreferredWidth() * scaleFactor);
				tcm.getColumn(i).setPreferredWidth(newWidth);
				tcm.getColumn(i).setWidth(newWidth);
				totalNewWidth += newWidth;
			}
			tcm.getColumn(tcm.getColumnCount() - 1).setPreferredWidth(
					(int) ((viewWidth - tcm.getTotalColumnWidth()) + tcm
							.getColumn(tcm.getColumnCount() - 1).getWidth()));
		}
	}

	/**
	 * When the table is resized this method will be called to change the number
	 * of visible rows.
	 */
	private void resizeTable() {
		int width;
		int height;
		width = scrollPane.getViewport().getWidth();
		height = scrollPane.getViewport().getHeight();
		scrollPane.getViewport().setSize(new Dimension(width, height));
	}

	/*
	 * Component Listener implementation. Need to resize our table/columns when
	 * when these events happen.
	 */

	@SuppressWarnings("unused")
	public void componentHidden(final ComponentEvent e) {
		resize();
	}

	@SuppressWarnings("unused")
	public void componentMoved(final ComponentEvent e) {
		resize();
	}

	@SuppressWarnings("unused")
	public void componentResized(final ComponentEvent e) {
		resize();
	}

	@SuppressWarnings("unused")
	public void componentShown(final ComponentEvent e) {
		resize();
	}

//	protected void showHelp() {
//		HelpPane help = new HelpPane();
//		// try {
//		help.setTitle("Tree Table Help");
//		help.setHTML(helpText);
//		// help
//		// .setPage("/com/ibm/foundation/toolkit/widget/treeTable/help/treeTableHelp.html");
//		help.show(this);
//		// } catch (IOException e) {
//		// e.printStackTrace();
//		// }
//	}

	public TableCellEditor createUniqueComboRenderer(final String dbId,
			final ArrayList<String> key, final ArrayList<String> items, final int level) {
		return table.createUniqueComboRenderer(dbId, key, items, level);
	}

	protected void printTable(final boolean preview) {
		PrintUtility print;
		Component root;
		String windowTitle = "";
		if (printTitle == null) {
			root = SwingUtilities
					.getAncestorOfClass(JInternalFrame.class, this);
			if (root != null && root instanceof JInternalFrame) {
				windowTitle = ((JInternalFrame) root).getTitle();
			}
		} else {
			windowTitle = printTitle;
		}
		print = new PrintUtility(this, windowTitle, preview);

		try {
			print.print();
		} catch (PrinterException pe) {

		}
	}

	public void setPrintTitle(final String title) {
		printTitle = title;
	}

	/**
	 * Add a list selection listener. <strong>Make sure to check that the value
	 * is not adjusting before trying to get data out of the tree.</strong>
	 *
	 * @param listener
	 */
	public void addListSelectionListener(final ListSelectionListener listener) {
		eventDispatcher.addListSelectionListener(listener);
	}

	public void addTreeSelectionListener(final TreeSelectionListener tsl) {
		eventDispatcher.addTreeSelectionListener(tsl);
	}

	public void removeListSelectionListener(final ListSelectionListener listener) {
		eventDispatcher.removeListSelectionListener(listener);
	}

	public void addRecord(final Data record, final int level) {
		table.addRecord(record, level);
	}

	protected void showChanges() {
		JDialog f = new JDialog();
		JPanel p = new JPanel(new GridBagLayout());
		RecordTablePane debugTable = new RecordTablePane();

		DisplayDescriptor dd = new BasicDisplayDescriptor();
		String[] id = new String[] { "TYPE", "LEVEL", "HAD_CHILDREN", "TK" };
		ArrayList<String> ids = new ArrayList<String>();
		ids.addAll(Arrays.asList(id));
		dd.setLabel("TYPE", "Type");
		for (int i = 0; i < debugTableIds.size(); i++) {
			String currentId = "<b>OLD</b>\n"
					+ debugTableIds.get(i).replace("_", "\n");
			ids.add(currentId);
			dd.setLabel(currentId, currentId);
		}

		for (int i = 0; i < debugTableIds.size(); i++) {
			String currentId = "<b>NEW</b>\n"
					+ debugTableIds.get(i).replace("_", "\n");
			ids.add(currentId);
			dd.setLabel(currentId, currentId);
		}
		dd.setLabel("LEVEL", "Level");
		dd.setLabel("HAD_CHILDREN", "Had Children");
		dd.setLabel("TK", "TK");
		debugTable.setVisibleRowCount(Math.max(Math
				.min(20, getChanges().size()), 10));
		p.add(debugTable, LayoutUtils.createConstraintsFillBoth());
		f.setContentPane(p);
		f.setTitle("Debug");
		debugTable.setLabels(dd, "");
		DataSet ds = new BasicDataSet(ids);
		for (KeyedChange c : getChanges()) {
			Data att = new BasicData();
			att.setDataItem("TYPE", c.getType());
			Data oldValue = c.getOldValue();
			Data newValue = c.getNewValue();
			for (int i = 0; i < debugTableIds.size(); i++) {
				att.setDataItem(ids.get(i + 4), oldValue == null ? null
						: oldValue.getValue(debugTableIds.get(i)));
				att.setDataItem(ids.get(i + 4 + debugTableIds.size()),
						newValue == null ? null : newValue
								.getValue(debugTableIds.get(i)));
			}
			att.setDataItem("LEVEL", c.getLevel());
			att.setDataItem("HAD_CHILDREN", c.hadChildren());
			att.setDataItem("TK", c.getTimeKey());
			ds.importRecord(att);
		}
		debugTable.setData(ds);
		f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
		f.setPreferredSize(new Dimension(800, 300));
		f.pack();
		f.setVisible(true);
	}

	protected class PopupMenuListener implements ActionListener, MouseListener {
		/*
		 * (non-Javadoc)
		 *
		 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
		 */
		public void actionPerformed(final ActionEvent e) {
			if (e.getActionCommand().equals(PrintableComponent.PRINT_COMMAND)) {
				printTable(false);
			} else if (e.getActionCommand().equals(
					PrintableComponent.PRINT_PREVIEW_COMMAND)) {
				printTable(true);
			} else if (e.getActionCommand().equals(TABLE_HELP_COMMAND)) {
//				showHelp();
			} else if (e.getActionCommand().equals(COLLAPSE_ENTIRE_TREE)) {
				table.collapseAll();
			} else if (e.getActionCommand().equals(EXPAND_ENTIRE_TREE)) {
				table.expandAll();
			} else if (e.getActionCommand().equals("View Changes") && debug) {
				showChanges();
			} else {
				throw new IllegalArgumentException(
						"Unrecognized action command.");
			}
		}

		/*
		 * (non-Javadoc)
		 *
		 * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
		 */
		@SuppressWarnings("unused")
		public void mouseClicked(final MouseEvent e) {
		}

		/*
		 * (non-Javadoc)
		 *
		 * @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
		 */
		@SuppressWarnings("unused")
		public void mouseEntered(final MouseEvent e) {
		}

		/*
		 * (non-Javadoc)
		 *
		 * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
		 */
		@SuppressWarnings("unused")
		public void mouseExited(final MouseEvent e) {
		}

		/*
		 * (non-Javadoc)
		 *
		 * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
		 */
		@SuppressWarnings("unused")
		public void mousePressed(final MouseEvent e) {
		}

		/*
		 * (non-Javadoc)
		 *
		 * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
		 */
		public void mouseReleased(final MouseEvent e) {
			if (e.isPopupTrigger()) {
				if (e.getSource().equals(table)) {
					if (popupMenu != null) {
						popupMenu.show(e.getComponent(), e.getX(), e.getY());
					}
				} else if (e.getSource().equals(table.getTableHeader())) {
					if (headerMenu != null) {
						headerMenu.show(e.getComponent(), e.getX(), e.getY());
					}
				}
			}
		}
	}

	/**
	 *
	 */
	public void clearChanges() {
		table.clearChanges();
	}

	public void setAutoClearChangeTracker(final boolean auto) {
		table.setAutoClearChangeTracker(auto);
	}

	private final String helpText = "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 3.2//EN\"> "
			+ "<!--Tree Table Help --> "
			+ "<!--Eric Lundin--> "
			+ "<!-- This file is for use with the HelpPane to support TreeTablePane --> "
			+ "<html> "
			+ "  <head> "
			+ "    <title> "
			+ "    </title> "
			+ "  </head> "
			+ "  <body> "
			+ "    <h2> "
			+ "      Overview "
			+ "    </h2> "
			+ "    <p> "
			+ "      A Tree Table is a component which combines the columnar view "
			+ "      of a table with the parent child relationships of a tree. "
			+ "      This allows data to be viewed in tree format while still "
			+ "      allowing data to be easily distinguishable through the use of "
			+ "      column headers. "
			+ "    </p> "
			+ "    <p> "
			+ "      Unlike a normal table a column&#39;s existance and position "
			+ "      is crucial for interpretation of the data. Therefore Tree "
			+ "      Tables do not allow: "
			+ "    </p> "
			+ "    <ul> "
			+ "      <li> "
			+ "        Sorting Column Data "
			+ "      </li> "
			+ "      <li> "
			+ "        Filtering Column Data "
			+ "      </li> "
			+ "      <li> "
			+ "        Column Reordering "
			+ "      </li> "
			+ "      <li> "
			+ "        Removing Columns "
			+ "      </li> "
			+ "    </ul> "
			+ "    <p> "
			+ "      <strong>Note:</strong> Tree Tables "
			+ "      <strong><em>do</em></strong> allow resizing columns and "
			+ "      saving preferences for column sizes. "
			+ "    </p> "
			+ "    <h2> "
			+ "      Viewing data "
			+ "    </h2> "
			+ "    <ul> "
			+ "      <li> "
			+ "        Open a node by double clicking the folder icon. "
			+ "      </li> "
			+ "      <li> "
			+ "        Closing a node is the same as opening it. "
			+ "      </li> "
			+ "      <li> "
			+ "        A &quot;leaf&quot; node is a node that has no children and "
			+ "        cannot have children. Therefor a leaf node may not be "
			+ "        opened. This is shown as a small gray circle. "
			+ "      </li> "
			+ "    </ul> "
			+ "    <h2> "
			+ "      Editing Data "
			+ "    </h2> "
			+ "    <p> "
			+ "      Editing Tree Table data is similar to editing table data. If "
			+ "      a given cell allows editing double click on that cell to "
			+ "      bring up the cell editor. Chnage the cell&#39;s value to the "
			+ "      desired value then hit the Enter key or change the focus to a "
			+ "      different cell to have the modification take effect. "
			+ "    </p> "
			+ "    <h2> "
			+ "      Printing the Tree Table "
			+ "    </h2> "
			+ "    <ul> "
			+ "      <li> "
			+ "        <strong>Setup</strong>  "
			+ "        <ul> "
			+ "          <li> "
			+ "            The Tree Table will print to paper the same way it is "
			+ "            currently displayed. This means the tree nodes which "
			+ "            should be printed should be expanded prior to choosing "
			+ "            the print option.  "
			+ "            <ul> "
			+ "              <li> "
			+ "                To quickly expand all nodes in order to print the "
			+ "                entire tree right click anywhere on the tree and "
			+ "                select &quot;Expand All&quot; "
			+ "              </li> "
			+ "            </ul> "
			+ "          </li> "
			+ "        </ul> "
			+ "      </li> "
			+ "      <li> "
			+ "        <strong>Printing</strong>  "
			+ "        <ul> "
			+ "          <li> "
			+ "            Initiate the printing process by right clicking on the "
			+ "            Tree Table data and selecting the &quot;Print "
			+ "            Table&quot; option. "
			+ "          </li> "
			+ "          <li> "
			+ "            The print dialog will display. Set the printer and "
			+ "            paper options to use in printing the table. "
			+ "          </li> "
			+ "          <li> "
			+ "            Press the &quot;Print&quot; button to send the Tree "
			+ "            Table to the printer or press the &quot;Cancel&quot; "
			+ "            button to abort the print process and return to the "
			+ "            table. "
			+ "          </li> "
			+ "        </ul> "
			+ "      </li> "
			+ "      <li> "
			+ "        <strong>Print Preview</strong>  "
			+ "        <ul> "
			+ "          <li> "
			+ "            Open the &quot;Print Preview&quot; window by right "
			+ "            clicking on the Tree Table data and selecting the "
			+ "            &quot;Print Preview&quot; option. "
			+ "          </li> "
			+ "          <li> "
			+ "            The &quot;Print Preview&quot; window will open, select "
			+ "            the default printer, set the default page format, and "
			+ "            display the first page.  "
			+ "            <ul> "
			+ "              <li> "
			+ "                <strong>Note:</strong> If there is no default printer set in the "
			+ "                operating system the first printer discovered will "
			+ "                be initially set. "
			+ "              </li> "
			+ "            </ul> "
			+ "          </li> "
			+ "          <li> "
			+ "            Use the page control in the top right corner of the "
			+ "            screen to traverse through the rendered pages.  "
			+ "            <ul> "
			+ "              <li> "
			+ "                The arrow buttons on the left and right with a line "
			+ "                next to them jump to the first and last pages, "
			+ "                respectively. "
			+ "              </li> "
			+ "              <li> "
			+ "                The plain arrow buttons, to the direct left and "
			+ "                right of the text box, jump to the previous and "
			+ "                next page respectively. "
			+ "              </li> "
			+ "              <li> "
			+ "                Page numbers may be entered directly into the "
			+ "                textbox in the center of the control. Press the "
			+ "                Enter key or transfer focus to a different part of "
			+ "                the window to have the change take effect. Entering "
			+ "                a page number not in the range of pages causes the "
			+ "                first or last page to be displayed depending on "
			+ "                whether the page number entered was less than 1 or "
			+ "                more than the total number of pages. "
			+ "              </li> "
			+ "            </ul> "
			+ "          </li> "
			+ "          <li> "
			+ "            Change the currently selected printer by using the "
			+ "            &quot;Printer&quot; drop down menu. "
			+ "          </li> "
			+ "          <li> "
			+ "            Change the page format by pressing the "
			+ "            &quot;Format&quot; button. This will bring up the page "
			+ "            format dialog. Choose any desired page format options "
			+ "            and press &quot;OK&quot; to apply the changes to the "
			+ "            rendering of the pages to be printed. Press "
			+ "            &quot;Cancel&quot; to abort making any changes. "
			+ "          </li> "
			+ "          <li> "
			+ "            When happy with the results of the preview using the "
			+ "            given printer and page format. Press the "
			+ "            &quot;Print&quot; button. "
			+ "          </li> "
			+ "          <li> "
			+ "            The Print Dialog will display with the options selected "
			+ "            in the &quot;Print Preview&quot; window already set.  "
			+ "            <ul> "
			+ "              <li> "
			+ "                <strong>Note:</strong> Changes may be made in the Print Dialog after "
			+ "                previewing but these changes may cause the Tree "
			+ "                Table to print differently than just previewed. "
			+ "              </li> "
			+ "            </ul> "
			+ "          </li> "
			+ "        </ul> "
			+ "      </li> "
			+ "      <li> "
			+ "        <strong>Notes</strong>  "
			+ "        <ul> "
			+ "          <li> "
			+ "            While in the &quot;Print Preview&quot; window the "
			+ "            window containing the Tree Table being previewed may "
			+ "            not be accessed. A message will be displayed requesting "
			+ "            the &quot;Print Preview&quot; window be closed before "
			+ "            working in the given window. "
			+ "          </li> "
			+ "          <li> "
			+ "            The print system will remove any selection(s) currently "
			+ "            on the Tree Table and restore them after printing is "
			+ "            complete. "
			+ "          </li> "
			+ "          <li> "
			+ "            The Tree Table will always be printed so that no rows "
			+ "            are cutoff at the end of a page. "
			+ "          </li> "
			+ "          <li> "
			+ "            Columns may be cutoff if they extend beyond the end of "
			+ "            a page. Take advantage of the &quot;Print Preview&quot; "
			+ "            window to ensure no data is cutoff before printing. "
			+ "          </li> "
			+ "        </ul> "
			+ "      </li> "
			+ "    </ul> " + "  </body> " + "</html> ";

}
